home *** CD-ROM | disk | FTP | other *** search
- /* OriginalSCSI.c */
- /*
- * OriginalSCSI.c
- * Copyright © 1992-93 Apple Computer Inc. All Rights Reserved.
- *
- * Talk to the Macintosh SCSI Handler using the "old" interface as defined in
- * Inside Mac IV. This is a synchronous call that executes a single SCSI Command
- * on a device. It does not support multiple busses or logical units.
- *
- * Calling Sequence:
- * OSErr OriginalSCSI(
- * short targetID,
- * const SCSI_CommandPtr scsiCommand,
- * unsigned short cmdBlockLength,
- * Boolean writeToDevice,
- * Ptr bufferPtr,
- * unsigned long transferSize,
- * unsigned long transferQuantum,
- * unsigned long completionTimeout,
- * unsigned short *stsBytePtr,
- * unsigned short *msgBytePtr
- * unsigned long *actualTransferCount
- * );
- * The parameters have the following meaning:
- *
- * targetID The SCSI Bus ID of the target (0 .. 6). Note that this
- * function can only access LUN zero.
- * scsiCommand The SCSI Command Block (6, 10, or 12 bytes). The command
- * block length will be determined by examining the command
- * parameter. This is standardized for all but
- * "vendor-specific" commands.
- * writeToDevice TRUE if this command writes to the device. FALSE if this
- * command reads from the device or does not require a data
- * phase.
- * bufferPtr The user data buffer for Read/Write commands. It should be
- * NULL if a data transfer phase is not used for this command.
- * (e.g. for Test Unit Ready).
- * transferSize The total number of bytes to transfer to or from the device.
- * transferQuantum This is needed to configure the transfer information block
- * (TIB). The following values are appropriate:
- * -- Set to zero for a one-shot blind transfer.
- * ActualTransferCount is not correctly returned.
- * -- Set to one if a polled transfer is needed. This is
- * useful for Request Sense or other management requests,
- * especially requests with a variable-length record. On
- * return, the actual number of bytes that were transferred
- * will be in the actualTransferCount variable.
- * -- Set to the transferSize value for a normal, "Blind"
- * transfer. This is the normal case for data requests.
- * ActualTransferCount will equal transferSize on success,
- * but does not correctly return the actual count on phase
- * errors. This, however, can be recovered from the Request
- * Sense record.
- * -- Set to a sub-multiple (such as a block length) for
- * requests that need re-synchronization between sectors.
- * -- Other values will likely result in errors.
- * completionTimeout The timeout (in Ticks) for the command. This should be short
- * for disks, but must be long for tape devices and some setup
- * requests, such as Mode Select.
- * stsBytePtr This short is set to the byte returned in the device's
- * Status Phase.
- * msgBytePtr This short is set to the byte returned in the device's
- * Command Complete message.
- * actualTransferCount This will be set to the number of "cycles" through the TIB
- * loop times the transferCount. This should equal the number
- * of bytes transferred if transferCount is set to one.
- * (Ignored if NULL.)
- * Return codes:
- * noErr normal
- * scCommErr no such device (selection error)
- * scPhaseErr user data buffer was the wrong size for the transfer.
- * Look at the status byte to see if this is a problem:
- * you may merely have given a large buffer size to
- * a variable-length request, such as Device Inquiry.
- * sc... other SCSI error.
- * statusErr Device returned "Check condition." The caller should
- * issue a Request Sense SCSI Command to this device.
- * controlErr Device returned "Busy"
- * ioErr Other (serious) device status. The caller should
- * examine the Status and Message bytes to determine
- * the problem.
- * paramErr Could not determine the command length.
- */
- #include <scsi.h>
- #include <Errors.h>
- #include <GestaltEqu.h>
- #include <Memory.h>
- #include <Events.h>
- #include "MacSCSICommand.h"
- #ifndef TRUE
- #define FALSE 0
- #define TRUE 1
- #endif
-
- /*
- * Execute a SCSI command using the original (Inside Mac IV) SCSI Manager.
- * Return codes:
- *
- * noErr normal
- * paramErr could not determine command length from command
- * scCommErr could not select this device or bus busy
- * sc... other scsi error
- * statusErr Device returned "Check condition"
- * controlErr Device returned "Busy" (Note: device error)
- * ioErr Other (serious) device status -- bug.
- */
- OSErr OriginalSCSI(
- short targetID, /* Device ID on this bus */
- const SCSI_CommandPtr scsiCommand, /* The actual scsi command */
- unsigned short cmdBlockLength, /* -> Length of CDB */
- Boolean writeToDevice, /* TRUE to write */
- Ptr bufferPtr, /* -> user data buffer */
- unsigned long transferSize, /* How much to transfer */
- unsigned long transferQuantum, /* TIB setup parameter */
- unsigned long completionTimeout, /* Ticks to wait */
- unsigned short *stsBytePtr, /* <- status phase byte */
- unsigned short *msgBytePtr, /* <- cmd complete messge */
- unsigned long *actualTransferCount
- );
-
- /*
- * SCSI command status (from status phase)
- */
- #define kScsiStatusGood 0x00 /* Normal completion */
- #define kScsiStatusCheckCondition 0x02 /* Need GetExtendedStatus */
- #define kScsiStatusConditionMet 0x04 /* For Compare Command? */
- #define kScsiStatusBusy 0x08 /* Device busy (self-test?) */
- #define kScsiStatusIntermediate 0x10 /* Intermediate status */
- #define kScsiStatusResConflict 0x18 /* Reservation conflict */
- #define kScsiStatusQueueFull 0x28 /* Target can't do command */
- #define kScsiStatusReservedMask 0x3e /* Vendor specific? */
-
- /*
- * This is the maximum number of times we try to grab the SCSI Bus
- */
- #define kMaxSCSIRetries 40 /* 10 seconds, 4 times/sec */
- /*
- * This test is TRUE if the SCSI bus status indicates "busy" (which is the case
- * if either the BSY or SEL bit is set).
- */
- #ifndef kScsiStatBSY
- #define kScsiStatBSY (1 << 6)
- #endif
- #ifndef kScsiStatSEL
- #define kScsiStatSEL (1 << 1)
- #endif
- #define ScsiBusBusy() ((SCSIStat() & (kScsiStatBSY | kScsiStatSEL)) != 0)
-
- static void NextFunction(void); /* Dummy function for OriginalSCSI size */
- static Boolean IsVirtualMemoryRunning(void);
- /*
- * These are bitmasks for the vmHoldMask variable. A bit is set if its associated
- * memory element has been held in protected (non-paged) memory.
- */
- #define kHoldFunction 0x0001 /* AsyncSCSI function code */
- #define kHoldStack 0x0002 /* Local variables */
- #define kHoldUserBuffer 0x0004 /* User data buffer, if any */
- #define kHoldCommandBlock 0x0008 /* SCSI Command Data Block */
-
- /*
- * Execute a SCSI command.
- * Returns the final status as noted above.
- */
- OSErr
- OriginalSCSI(
- short targetID, /* SCSI device on this bus */
- const SCSI_CommandPtr scsiCommand, /* The actual scsi command */
- unsigned short cmdBlockLength, /* -> Length of CDB */
- Boolean writeToDevice, /* TRUE to write */
- Ptr bufferPtr, /* -> user data buffer */
- unsigned long transferSize, /* How much to transfer */
- unsigned long transferQuantum, /* TIB setup parameter */
- unsigned long completionTimeout, /* Ticks to wait */
- unsigned short *stsBytePtr, /* <- status phase byte */
- unsigned short *msgBytePtr, /* <- cmd complete messge */
- unsigned long *actualTransferCount
- )
- {
- OSErr status; /* Final status */
- OSErr completionStatus; /* Status from ScsiComplete */
- short totalTries; /* Get/Select retries */
- short getTries; /* Get retries */
- short iCount; /* Bus free counter */
- unsigned long watchdog; /* Timeout after this */
- unsigned long myTransferCount; /* Gets TIB loop counter */
- /*
- * The TIB has the following format:
- * [0] scInc user buffer transferQuantum or transferSize
- * [1] scAdd &theTransferCount 1
- * [2] scLoop -> tib[0] transferSize / transferQuantum
- * [3] scStop
- * The intent of this is to return, in actualTransferCount, the number
- * of times we cycled through the tib[] loop. This will be the actual
- * transfer count if transferQuantum equals one, or the number of
- * "blocks" if transferQuantum is the length of one sector.
- */
- SCSIInstr tib[4]; /* Current TIB */
- /*
- * The following parameters are used to manage virtual memory. The code
- * is taken from the DTS SCSI Sample Driver.
- */
- unsigned short vmHoldMask;
- unsigned long vmFunctionSize;
- char *vmProtectedStackBase; /* Last local variable */
- /*
- * These values are used to compute the size of the stack that we must hold in
- * protected (non-virtual) memory. kSCSIManagerStackEstimate is an estimate.
- */
- #define kSCSILocalVariableSize ( \
- (unsigned long) (((Ptr) &status) - ((Ptr) &vmProtectedStackBase)) \
- )
- #define kSCSIManagerStackEstimate 512
- #define kSCSIProtectedStackSize (kSCSIManagerStackEstimate + kSCSILocalVariableSize)
-
- status = noErr;
- vmHoldMask = 0;
- /*
- * If there is a data transfer, setup the tib.
- */
- myTransferCount = 0;
- if (transferQuantum == 0)
- transferQuantum = transferSize;
- if (bufferPtr != NULL) {
- tib[0].scOpcode = scInc;
- tib[0].scParam1 = (unsigned long) bufferPtr;
- tib[0].scParam2 = transferQuantum;
- tib[1].scOpcode = scAdd;
- tib[1].scParam1 = (unsigned long) &myTransferCount;
- tib[1].scParam2 = transferQuantum;
- tib[2].scOpcode = scLoop;
- tib[2].scParam1 = (-2 * sizeof (SCSIInstr));
- tib[2].scParam2 = transferSize / tib[0].scParam2;
- tib[3].scOpcode = scStop;
- tib[3].scParam1 = 0;
- tib[3].scParam2 = 0;
- }
- if (IsVirtualMemoryRunning()) {
-
- /*
- * Virtual memory is active. Lock all of the memory segments that we
- * need in "real" memory (i.e. non-paged pool) for the duration of the
- * call. Since we need to back out of VM holds if there are errors,
- * we'll use bits in vmHoldMask to record the status of our attempts.
- *
- * Note: in a real application or driver, the user buffers should be
- * held outside of the SCSI Manager code:
- * HoldMemory(data buffer);
- * HoldMemory(autosense buffer);
- * status = CallSCSIManager(...);
- * UnholdMemory(...);
- *
- * First, hold the MacSCSI function. It starts at AsyncSCSI and
- * extends to the start of the next function. This is marked by a
- * dummy function. The left-margin comments indicate the value
- * of vmHoldCount if the indicated HoldMemory succeeded. This is not
- * needed for drivers.
- */
- vmFunctionSize =
- (unsigned long) NextFunction - (unsigned long) OriginalSCSI;
- status = HoldMemory(OriginalSCSI, vmFunctionSize);
- if (status == noErr)
- vmHoldMask |= kHoldFunction;
- if (status == noErr) {
- /*
- * Hold a chunk of stack space to call the SCSI Manager and to
- * protect our local variables. This is always needed, as drivers
- * can be called from application contexts.
- */
- vmProtectedStackBase =
- (char *) &vmProtectedStackBase - kSCSIManagerStackEstimate;
- status = HoldMemory(vmProtectedStackBase, kSCSIProtectedStackSize);
- if (status == noErr)
- vmHoldMask |= kHoldStack;
- }
- if (status == noErr) {
- /*
- * Lock down the command block. In this sample, we allocated
- * the command block in the application heap. A driver would
- * typically allocate it in the System Heap and, hence, would
- * not require this call.
- */
- status = HoldMemory((Ptr) scsiCommand, cmdBlockLength);
- if (status == noErr)
- vmHoldMask |= kHoldCommandBlock;
- }
- if (status == noErr && bufferPtr != NULL) {
- /*
- * Lock down the user buffer, if any. In a real-world application
- * or driver, this would be done before calling the SCSI interface.
- */
- status = HoldMemory(bufferPtr, transferSize);
- if (status != noErr)
- vmHoldMask |= kHoldStack;
- }
- if (status != noErr)
- goto exit;
- }
- /*
- * Arbitrate for the scsi bus. This will fail if some other device is
- * accessing the bus at this time (which is unlikely).
- *
- *** Do not set breakpoints or call any functions that may require device
- *** I/O (such as display code that accesses font resources between
- *** SCSIGet and SCSIComplete,
- *
- */
- for (totalTries = 0; totalTries < kMaxSCSIRetries; totalTries++) {
- for (getTries = 0; getTries < 4; getTries++) {
- /*
- * Wait for the bus to go free.
- */
- watchdog = TickCount() + 300; /* 5 second timeout */
- while (ScsiBusBusy()) {
- if (TickCount() > watchdog) {
- status = scArbNBErr;
- goto exit;
- }
- }
- /*
- * The bus is free, try to grab it
- */
- for (iCount = 0; iCount < 4; iCount++) {
- if ((status = SCSIGet()) == noErr)
- break;
- }
- if (status == noErr)
- break; /* Success: we have the bus */
- /*
- * The bus became busy again. Try to wait for it to go free.
- */
- for (iCount = 0; iCount < 100 && ScsiBusBusy(); iCount++)
- ;
- } /* The getTries loop */
- if (status != noErr) {
- /*
- * The SCSI Manager thinks the bus is not busy and not selected,
- * but "someone" has set its internal semaphore that signals
- * that the SCSI Manager itself is busy. The application will have
- * to handle this problem. (We tried getTries * 4 times).
- */
- goto exit;
- }
- /*
- * We now own the SCSI bus. Try to select the device.
- */
- if ((status = SCSISelect(targetID)) != noErr)
- goto exit;
- /*
- * From this point on, we must exit through SCSIComplete() even if an
- * error is detected. Send a command to the selected device. There are
- * several failure modes, including an illegal command (such as a
- * write to a read-only device). If the command failed because of
- * "device busy", we will try it again.
- */
- status = SCSICmd((Ptr) scsiCommand, cmdBlockLength);
- if (status == noErr && bufferPtr != NULL) {
- /*
- * This command requires a data transfer.
- */
- if (writeToDevice) {
- if (transferQuantum == 1)
- status = SCSIWrite((Ptr) tib);
- else {
- status = SCSIWBlind((Ptr) tib);
- }
- }
- else {
- if (transferQuantum == 1)
- status = SCSIRead((Ptr) tib);
- else {
- status = SCSIRBlind((Ptr) tib);
- }
- }
- }
- finish:
- /*
- * SCSIComplete "runs" the bus-phase algorithm until the bitter end,
- * returning the status and command-completion message bytes..
- */
- completionStatus = SCSIComplete(
- (short *) stsBytePtr,
- (short *) msgBytePtr,
- completionTimeout
- );
- /*
- * If we have an error here, return as the "final" status.
- *
- */
- if (completionStatus != noErr)
- status = completionStatus;
- else {
- /*
- * ScsiComplete is happy. If the device is busy, Pause for 1/4
- * second and try again.
- */
- if (*stsBytePtr == kScsiStatusBusy) {
- watchdog = TickCount() + 15;
- while (TickCount() < watchdog)
- ;
- continue; /* Do next totalTries attempt */
- }
- }
- /*
- * This is the normal exit (success) or final failure exit.
- */
- break;
- } /* totalTries loop */
- exit:
- /*
- * If we held memory, unhold it now. We ignore UnholdMemory errors:
- * there isn't much we can do about them. Note that this must be
- * done by driver or asynchronous completion routines.
- */
- if ((vmHoldMask & kHoldUserBuffer) != 0)
- (void) UnholdMemory(bufferPtr, transferSize);
- if ((vmHoldMask & kHoldCommandBlock) != 0)
- (void) UnholdMemory((Ptr) scsiCommand, cmdBlockLength);
- if ((vmHoldMask & kHoldStack) != 0)
- (void) UnholdMemory(vmProtectedStackBase, kSCSIProtectedStackSize);
- if ((vmHoldMask & kHoldFunction) != 0)
- (void) UnholdMemory(OriginalSCSI, vmFunctionSize);
- /*
- * Return the number of bytes transferred to the caller. If the caller
- * supplied an actual count and the count is no greater than the maximum,
- * ignore any phase errors.
- */
- if (actualTransferCount != NULL) {
- *actualTransferCount = myTransferCount;
- if (status == scPhaseErr
- && writeToDevice == FALSE
- && myTransferCount <= transferSize
- && myTransferCount > 0)
- status = noErr;
- }
- /*
- * Return an artificial error if the device returns a non-zero status:
- * statusErr Caller should issue RequestSense.
- * controlErr Device is busy (self-test?) try again later.
- * ioErr Something is dreadfully wrong.
- * scPhaseErr This may not be a "real" error -- it may mean that the
- * user data buffer was too large for the transfer. (See
- * the check above.)
- * Also, there is a bug in the combination of System 7.0.1 and the 53C96
- * that may cause the real SCSI Status Byte to be in the Message byte.
- */
- if (*stsBytePtr == kScsiStatusGood
- && *msgBytePtr == kScsiStatusCheckCondition)
- *stsBytePtr = kScsiStatusCheckCondition;
- if (status == noErr) {
- switch (*stsBytePtr) {
- case kScsiStatusGood: break;
- case kScsiStatusCheckCondition: status = statusErr; break;
- case kScsiStatusBusy: status = controlErr; break;
- default: status = ioErr; break;
- }
- }
- return (status);
- }
-
- static void NextFunction(void) { } /* Dummy function for OriginalSCSI size */
-
- static Boolean
- IsVirtualMemoryRunning(void)
- {
- OSErr status;
- long response;
-
- status = Gestalt(gestaltVMAttr, &response);
- /*
- * VM is active iff Gestalt succeeded and the response is appropriate.
- */
- return (status == noErr && ((response & (1 << gestaltVMPresent)) != 0));
- }
-